/*
 * 一、request：
 *    1. 说明：封装对后台的请求，可以选择自动处理一些异常。
 *    2. 参数：
 *        - url：          后台地址，必填，String，如："/user/add"
 *        - params：       请求参数，选填，Object，，默认值：{}
 *        - config：       axios参数，选填，Object，默认值：{}
 *        - autoErrorRes： 是否自动处理响应错误，选填，Boolean，默认值：true
 *        - autoErrorData：是否自动处理后台错误，选填，Boolean，默认值：true
 *        - autoCancel：   离开路由时是否自动取消请求，选填，Boolean，默认值：true
 *    3. 返回：
 *        - 成功：Promise.resolve(请求成功后的结果：response.data)
 *        - 失败：
 *            - 请求异常：Promise.reject(http响应错误)
 *            - 请求失败：Promise.reject(请求失败后的结果：response.data)
 *    4. 约定后台返回数据格式：
 *        response.data = {
 *          "code": 1,                    // 成功/失败标识，1=成功，-1=失败
 *          "object": {},                   // 成功时可选参数，请求的响应数据
 *          "msg": "用户名字重复"  // 失败时必需参数，错误提示
 *        }
 *
 * 二、sessionRequest：
 *    1. 说明：利用sessionStorage缓存请求，可以选择outTime，其他同request。
 *    2. 参数：
 *        - outTime：距离上次请求多少秒后需要重新请求，选填，Integer，小于0表示不重新请求，默认值：-1
 *
 * 三、localRequest：
 *    1. 说明：利用localStorage缓存请求，可以选择outTime，其他同request。
 *    2. 参数：
 *        - outTime：距离上次请求多少秒后需要重新请求，选填，Integer，小于0表示不重新请求，默认值：604800（一周）
 *
 **/
import axios from 'axios'
import qs from 'qs'
import store from '../store/index'
import { ResultCode } from '@/constants/constants'
import { MessageBox } from 'element-ui'
import AutoError from '@utils/AutoError'
import constants from '@store/user/constants'

/**
 * 初始化对象，进行全局配置
 */
const axiosCustom = axios.create({
  baseURL: 'http://lixunda.top:2100/',
  withCredentials: true
})

document.axios = axiosCustom

// 拦截请求，在请求中加入token，后台使用jwt
axiosCustom.interceptors.request.use(config => {
  config.headers.Authorization =
    (store.state.user.userInfo && store.state.user.userInfo['token']) ||
    localStorage.getItem('token') || ''
  return config
})

axiosCustom.interceptors.response.use(response => {
  if (response.status === 400 && response.data && response.data.code === ResultCode.OK) {
    if (response.headers['AUTO_LOGIN']) {
      let userInfo = response.headers['AUTO_LOGIN_INFO']
      if (userInfo) {
        userInfo = JSON.parse(userInfo)
        console.log('自动登录成功：' + JSON.stringify(userInfo))
        store.commit('user/' + constants.MUTATIONS_SET_LOGIN_USER_INFO, userInfo)
        localStorage.setItem('token', userInfo['token'])
      }
    }
  }
  return response
})

/**
 * 带有回调的请求
 * @param {*} url 请求路径
 * @param {*} type 请求类型，默认post
 * @param {*} params 请求参数，或者一些其他配置参数
 * @param {*} config 配置信息
 * @param {*} autoErrorData 是否自动处理数据错误，默认为true
 * @param {*} successCallback Function(data) 成功回调,当code==200时触发，data为获取到的数据
 * @param {*} errorCallback Function(data) 失败回调，当code!=200时触发,data为获取到的数据
 * @param {*} fullCallback 全部回调，始终会调用一次，无论是成功或者失败
 */
const fallbackRequest = (url, type = 'post', params = {}, config = {}, autoErrorData = true, successCallback = null, errorCallback = null, fullCallback = null) => {
  return request(url, type, params, config, true, autoErrorData, true).then(data => {
    // 全值回调
    if (fullCallback) {
      fullCallback(data)
      fullCallback = null
    }
    if (data && data.code === ResultCode.OK && successCallback) {
      successCallback(data)
    } else {
      if (errorCallback) {
        errorCallback(data)
      }
    }
    return data
  }).catch(error => {
    if (errorCallback) {
      errorCallback(error)
      errorCallback = null
    }
    if (fullCallback) {
      fullCallback(error)
      fullCallback = null
    }
    return Promise.reject(error)
  })
}

/**
 * session缓存带有回调的请求
 * @param {*} url 请求路径
 * @param {*} type 请求类型，默认post
 * @param {*} params 请求参数，或者一些其他配置参数
 * @param {*} config 配置信息
 * @param {Number|String} outTime 保存时长，单位秒
 * @param {Boolean} refresh 是否是刷新操作，刷新操作会重新获取数据并缓存
 * @param {*} autoErrorData 是否自动处理数据错误，默认为true
 * @param {*} successCallback Function(data) 成功回调,当code==200时触发，data为获取到的数据
 * @param {*} errorCallback Function(data) 失败回调，当code!=200时触发,data为获取到的数据
 * @param {*} fullCallback 全部回调，始终会调用一次，无论是成功或者失败
 */
const sessionFallbackRequest = (url, type = 'post', params = {}, config = {}, outTime = -1, refresh = true, autoErrorData = true, successCallback = null, errorCallback = null, fullCallback = null) => {
  return sessionRequest(url, type, params, config, outTime, refresh, true, autoErrorData, true).then(data => {
    // 全值回调
    if (fullCallback) {
      fullCallback(data)
      fullCallback = null
    }
    if (data && data.code === ResultCode.OK && successCallback) {
      successCallback(data)
    } else {
      errorCallback(data)
    }
    return data
  }).catch(error => {
    if (fullCallback) {
      fullCallback(error)
      fullCallback = null
    }
    return Promise.reject(error)
  })
}

/**
 * localStore缓存带有回调的请求
 * @param {*} url 请求路径
 * @param {*} type 请求类型，默认post
 * @param {*} params 请求参数，或者一些其他配置参数
 * @param {*} config 配置信息
 * @param {Number|String} outTime 保存时长，单位秒
 * @param {Boolean} refresh 是否是刷新操作，刷新操作会重新获取数据并缓存
 * @param {*} autoErrorData 是否自动处理数据错误，默认为true
 * @param {*} successCallback Function(data) 成功回调,当code==200时触发，data为获取到的数据
 * @param {*} errorCallback Function(data) 失败回调，当code!=200时触发,data为获取到的数据
 * @param {*} fullCallback 全部回调，始终会调用一次，无论是成功或者失败
 */
const localFallbackRequest = (url, type = 'post', params = {}, config = {}, outTime = -1, refresh = true, autoErrorData = true, successCallback = null, errorCallback = null, fullCallback = null) => {
  return localRequest(url, type, params, config, outTime, refresh, true, autoErrorData, true).then(data => {
    // 全值回调
    if (fullCallback) {
      fullCallback(data)
      fullCallback = null
    }
    if (data && data.code === ResultCode.OK && successCallback) {
      successCallback(data)
    } else {
      errorCallback(data)
    }
    return data
  }).catch(error => {
    if (fullCallback) {
      fullCallback(error)
      fullCallback = null
    }
    return Promise.reject(error)
  })
}

/**
 * fallbackRequest请求的get请求回调
 * @param {*} url 请求路径
 * @param {*} params 请求参数，或者一些其他配置参数
 * @param {*} config 配置
 * @param {*} autoErrorData 是否自动处理数据错误，默认为true
 * @param {*} successCallback Function(data) 成功回调,当code==200时触发，data为获取到的数据
 * @param {*} errorCallback Function(data) 失败回调，当code!=200时触发,data为获取到的数据
 * @param {*} fullCallback 全部回调，始终会调用一次，无论是成功或者失败
 */
export const fallbackRequestGet = (url, params = {}, config = {}, autoErrorData = true, successCallback = null, errorCallback = null, fullCallback = null) => {
  return fallbackRequest(url, 'get', params, config, autoErrorData, successCallback, errorCallback, fullCallback)
}

/**
 * sessionFallbackRequest请求的get请求回调
 * @param {*} url 请求路径
 * @param {*} params 请求参数，或者一些其他配置参数
 * @param {*} config 配置
 * @param {Number|String} outTime 保存时长，单位秒
 * @param {Boolean} refresh 是否是刷新操作，刷新操作会重新获取数据并缓存
 * @param {*} autoErrorData 是否自动处理数据错误，默认为true
 * @param {*} successCallback Function(data) 成功回调,当code==200时触发，data为获取到的数据
 * @param {*} errorCallback Function(data) 失败回调，当code!=200时触发,data为获取到的数据
 * @param {*} fullCallback 全部回调，始终会调用一次，无论是成功或者失败
 */
export const sessionFallbackRequestGet = (url, params = {}, config = {}, outTime = -1, refresh = false, autoErrorData = true, successCallback = null, errorCallback = null, fullCallback = null) => {
  return sessionFallbackRequest(url, 'get', params, config, outTime, refresh, autoErrorData, successCallback, errorCallback, fullCallback)
}

/**
 * localStore缓存请求的get请求回调
 * @param {*} url 请求路径
 * @param {*} params 请求参数，或者一些其他配置参数
 * @param {*} config 配置
 * @param {Number|String} outTime 保存时长，单位秒
 * @param {Boolean} refresh 是否是刷新操作，刷新操作会重新获取数据并缓存
 * @param {*} autoErrorData 是否自动处理数据错误，默认为true
 * @param {*} successCallback Function(data) 成功回调,当code==200时触发，data为获取到的数据
 * @param {*} errorCallback Function(data) 失败回调，当code!=200时触发,data为获取到的数据
 * @param {*} fullCallback 全部回调，始终会调用一次，无论是成功或者失败
 */
export const localFallbackRequestGet = (url, params = {}, config = {}, outTime = -1, refresh = false, autoErrorData = true, successCallback = null, errorCallback = null, fullCallback = null) => {
  return localFallbackRequest(url, 'get', params, config, outTime, refresh, autoErrorData, successCallback, errorCallback, fullCallback)
}

/**
 * fallbackRequest请求的post请求回调
 * @param {*} url 请求路径
 * @param {*} params 请求参数，或者一些其他配置参数
 * @param {*} config 配置
 * @param {*} autoErrorData 是否自动处理数据错误，默认为true
 * @param {*} successCallback Function(data) 成功回调,当code==200时触发，data为获取到的数据
 * @param {*} errorCallback Function(data) 失败回调，当code!=200时触发,data为获取到的数据
 * @param {*} fullCallback 全部回调，始终会调用一次，无论是成功或者失败
 */
export const fallbackRequestPost = (url, params = {}, config = {}, autoErrorData = true, successCallback = null, errorCallback = null, fullCallback = null) => {
  return fallbackRequest(url, 'post', params, config, autoErrorData, successCallback, errorCallback, fullCallback)
}

/**
 * sessionFallbackRequest请求的post请求回调
 * @param {*} url 请求路径
 * @param {*} params 请求参数，或者一些其他配置参数
 * @param {*} config 配置
 * @param {Number|String} outTime 保存时长，单位秒
 * @param {Boolean} refresh 是否是刷新操作，刷新操作会重新获取数据并缓存
 * @param {*} autoErrorData 是否自动处理数据错误，默认为true
 * @param {*} successCallback Function(data) 成功回调,当code==200时触发，data为获取到的数据
 * @param {*} errorCallback Function(data) 失败回调，当code!=200时触发,data为获取到的数据
 * @param {*} fullCallback 全部回调，始终会调用一次，无论是成功或者失败
 */
export const sessionFallbackRequestPost = (url, params = {}, config = {}, outTime = -1, refresh = false, autoErrorData = true, successCallback = null, errorCallback = null, fullCallback = null) => {
  return sessionFallbackRequest(url, 'post', params, config, outTime, refresh, autoErrorData, successCallback, errorCallback, fullCallback)
}

/**
 * localStore缓存请求的post请求回调
 * @param {*} url 请求路径
 * @param {*} params 请求参数，或者一些其他配置参数
 * @param {*} config 配置
 * @param {Number|String} outTime 保存时长，单位秒
 * @param {Boolean} refresh 是否是刷新操作，刷新操作会重新获取数据并缓存
 * @param {*} autoErrorData 是否自动处理数据错误，默认为true
 * @param {*} successCallback Function(data) 成功回调,当code==200时触发，data为获取到的数据
 * @param {*} errorCallback Function(data) 失败回调，当code!=200时触发,data为获取到的数据
 * @param {*} fullCallback 全部回调，始终会调用一次，无论是成功或者失败
 */
export const localFallbackRequestPost = (url, params = {}, config = {}, outTime = -1, refresh = false, autoErrorData = true, successCallback = null, errorCallback = null, fullCallback = null) => {
  return localFallbackRequest(url, 'post', params, config, outTime, refresh, autoErrorData, successCallback, errorCallback, fullCallback)
}

/**
 * 普通请求
 * @param {String} url 后台地址，必填，String，如："/user/add"
 * @param {String} type 请求类型，默认Post请求
 * @param {Object} params 请求参数，选填，Object，，默认值：{}
 * @param {Object} config axios参数，选填，Object，默认值：{}
 * @param {Boolean} autoErrorRes 是否自动处理响应错误，选填，Boolean，默认值：true
 * @param {Boolean} autoErrorData 是否自动处理后台错误，选填，Boolean，默认值：true
 * @param {Boolean} autoCancel  离开路由时是否自动取消请求，选填，Boolean，默认值：true
 * @return {Promise<unknown>|Promise<Promise<never>>}
 */
export const request = (url, type = 'post', params = {},
  config = {}, autoErrorRes = true, autoErrorData = true,
  autoCancel = true) => {
  // 设置cancelToken
  if (autoCancel) {
    config = Object.assign({ cancelToken: store.state.request.token }, config)
  }
  // 组合参数
  const args = Object.assign({
    'method': type,
    'url': url,
    'data': params
  }, config)
  // 处理url传参
  if (!['put', 'post', 'patch'].includes(args.method.toLowerCase())) {
    args['params'] = args['params'] || args['data']
    args['paramsSerializer'] = args['paramsSerializer'] || function (params) {
      // noinspection JSUnresolvedFunction
      return qs.stringify(params, { arrayFormat: 'indices' })
    }
  }
  // 发送请求
  // noinspection JSValidateTypes
  return axiosCustom(args).then((res) => {
    // 自动处理返回格式错误
    if (autoErrorData && res.data.hasOwnProperty('code') && res.data.code !== ResultCode.OK) {
      const errMsg = res.data.msg || '未知的服务器错误，请联系管理员！'
      const errCod = res.data.code
      let i
      // noinspection JSIgnoredPromiseFromCall
      for (i = 0; i < AutoError.length; i++) {
        const item = AutoError[i]
        if (item) {
          const r = item(res.data)
          if (r) {
            break
          }
        }
      }
      if (i >= AutoError.length) {
        MessageBox.alert(errMsg, '请求异常：' + errCod, {
          confirmButtonText: '确定',
          type: 'error',
          callback: () => {
          }
        }).then(() => {})
      }
      return Promise.reject(res.data)
    }
    return res.data
  }, (error) => {
    error.response = error.response || {}
    const errStatus = error.response.status || -100
    if (autoErrorRes && error.message) {
      // noinspection JSIgnoredPromiseFromCall
      MessageBox.alert(error.response.data ? error.response.data.message : '网络请求异常，请联系管理员！', '请求异常：' + errStatus, { confirmButtonText: '确定' })
    }
    return Promise.reject(error)
  })
}

/**
 * 使用session缓存的请求
 * @param {String} url 地址
 * @param {String} type 请求方式
 * @param {Object} params 参数
 * @param {Object} config 配置参数
 * @param {String|Number} outTime 缓存时间，单位秒
 * @param {Boolean} refresh 控制参数，当为true的时候表示刷新，或重新获取数据
 * @param {Boolean} autoErrorRes 是否自动处理错误
 * @param {Boolean} autoErrorData 是否自动处理后台错误，选填，Boolean，默认值：true
 * @param {Boolean} autoCancel 是否自动取消请求
 * @return {Promise<unknown>|Promise<Promise<never>>}
 */
export const sessionRequest = (url, type = 'post', params = {}, config = {}, outTime = -1, refresh = false, autoErrorRes = true, autoErrorData = true, autoCancel = true) => {
  const itemKey = url + '#' + JSON.stringify(params) + JSON.stringify(config)
  let itemVal = sessionStorage.getItem(itemKey)
  const nowTime = new Date().getTime()
  if (itemVal && !refresh) {
    itemVal = JSON.parse(itemVal)
    const overTime = nowTime - itemVal.lastTime
    if (outTime < 0 || overTime < outTime * 1000) {
      return Promise.resolve(itemVal.data)
    }
  }
  return request(url, type, params, config, autoErrorRes, autoErrorData, autoCancel).then(data => {
    sessionStorage.setItem(itemKey, JSON.stringify({
      'lastTime': nowTime,
      'data': data
    }))
    return data
  })
}

/**
 * 使用localStorage缓存的请求
 * @param {String} url 地址
 * @param {String} type 请求方式
 * @param {Object} params 参数
 * @param {Object} config 配置参数
 * @param {String|Number} outTime 缓存时间，单位秒
 * @param {Boolean} refresh 控制参数，当为true的时候表示刷新，或重新获取数据
 * @param {Boolean} autoErrorRes 是否自动处理错误
 * @param {Boolean} autoErrorData 是否自动处理后台错误，选填，Boolean，默认值：true
 * @param {Boolean} autoCancel 是否自动取消请求
 * @return {Promise<unknown>|Promise<Promise<never>>}
 */
export const localRequest = (url, type = 'post', params = {}, config = {}, outTime = 604800, refresh = false, autoErrorRes = true, autoErrorData = true, autoCancel = true) => {
  const itemKey = url + '#' + JSON.stringify(params) + JSON.stringify(config)
  let itemVal = localStorage.getItem(itemKey)
  const nowTime = new Date().getTime()
  if (itemVal && !refresh) {
    itemVal = JSON.parse(itemVal)
    const overTime = nowTime - itemVal.lastTime
    if (outTime < 0 || overTime < outTime * 1000) {
      return Promise.resolve(itemVal.data)
    }
  }
  return request(url, type, params, config, autoErrorRes, autoErrorData, autoCancel).then(data => {
    localStorage.setItem(itemKey, JSON.stringify({
      'lastTime': nowTime,
      'data': data
    }))
    return data
  })
}
